package com.jpro.module.infra.controller.admin.file;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.common.util.object.BeanUtils;
import com.jpro.framework.common.util.servlet.ServletUtils;
import com.jpro.module.infra.api.file.dto.ImgCutDto;
import com.jpro.module.infra.api.file.vo.ImgCutVo;
import com.jpro.module.infra.api.file.vo.ImgRemoteDownVo;
import com.jpro.module.infra.config.JproProperties;
import com.jpro.module.infra.controller.admin.file.convert.FileDOConvert;
import com.jpro.module.infra.controller.admin.file.handler.FileHandler;
import com.jpro.module.infra.controller.admin.file.image.ImageUtil;
import com.jpro.module.infra.controller.admin.file.vo.file.*;
import com.jpro.module.infra.dal.dataobject.file.FileDO;
import com.jpro.module.infra.enums.ResizeMode;
import com.jpro.module.infra.service.file.DocConvertService;
import com.jpro.module.infra.service.file.FileService;
import com.jpro.module.infra.util.MediaUtil;
import com.jpro.module.infra.util.PdfUtil;
import com.jpro.module.infra.util.SiteUtil;
import com.jprocms.module.cms.api.GlobalConfigApi;
import com.jprocms.module.cms.api.SiteConfigApi;
import com.jprocms.module.cms.api.dto.GlobalConfigRespVO;
import com.jprocms.module.cms.api.dto.SiteConfigDto;
import com.jprocms.module.cms.api.dto.SiteConfigWatermarkDto;
import com.jprocms.module.cms.api.dto.UploadConfig;
import com.jprocms.module.cms.enums.EnumWaterMarkType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.FileCopyUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;

import javax.annotation.security.PermitAll;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jpro.framework.common.pojo.CommonResult.success;
import static com.jpro.module.infra.controller.admin.file.vo.file.FileValidVo.*;
import static com.jpro.module.infra.enums.ErrorCodeConstants.*;
import static com.jpro.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
import static org.apache.commons.lang3.StringUtils.EMPTY;

@Tag(name = "管理后台 - 文件存储")
@RestController
@RequestMapping("/infra/file")
@Validated
@Slf4j
public class FileController {

    private FileService fileService;

    @Autowired
    @Nullable
    private DocConvertService docConvertService;

    private JproProperties jproProperties;

    private GlobalConfigApi globalConfigApi;

    private SiteConfigApi siteConfigApi;

    public FileController(FileService fileService, JproProperties jproProperties,
                          GlobalConfigApi globalConfigApi, SiteConfigApi siteConfigApi) {
        this.fileService = fileService;
        this.jproProperties = jproProperties;
        this.globalConfigApi = globalConfigApi;
        this.siteConfigApi = siteConfigApi;
    }

    @PostMapping("/upload")
    @Operation(summary = "上传文件", description = "模式一：后端上传文件")
    public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
        MultipartFile file = uploadReqVO.getFile();
        String path = uploadReqVO.getPath();
        String url = fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()));
        return success(url);
    }

    @GetMapping("/presigned-url")
    @Operation(summary = "获取文件预签名地址", description = "模式二：前端上传文件：用于前端直接上传七牛、阿里云 OSS 等文件存储器")
    public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {
        return success(fileService.getFilePresignedUrl(path));
    }

    @PostMapping("/create")
    @Operation(summary = "创建文件", description = "模式二：前端上传文件：配合 presigned-url 接口，记录上传了上传的文件")
    public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
        return success(fileService.createFile(createReqVO));
    }

    @DeleteMapping("/delete")
    @Operation(summary = "删除文件")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('infra:file:delete')")
    public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
        fileService.deleteFile(id);
        return success(true);
    }

    @PostMapping("/batchDelete")
    @Operation(summary = "批量删除文件")
    @Parameter(name = "ids", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('infra:file:delete')")
    public CommonResult<Boolean> batchDelete(@RequestBody List<Long> ids) throws Exception {
        fileService.batchDeleteFile(ids);
        return success(true);
    }

    @GetMapping("/{configId}/get/**")
    @PermitAll
    @Operation(summary = "下载文件")
    @Parameter(name = "configId", description = "配置编号", required = true)
    public void getFileContent(HttpServletRequest request,
                               HttpServletResponse response,
                               @PathVariable("configId") Long configId, @RequestParam(value = "name", required = false) String name) throws Exception {
        // 获取请求的路径
        String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
        if (StrUtil.isEmpty(path)) {
            throw new IllegalArgumentException("结尾的 path 路径必须传递");
        }
        path = URLUtil.decode(path);

        // 读取内容
        byte[] content = fileService.getFileContent(configId, path);
        if (content == null) {
            log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        writeAttachment(response, path, content);
    }

    @GetMapping("/page")
    @Operation(summary = "获得文件分页")
    @PreAuthorize("@ss.hasPermission('infra:file:query')")
    public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {
        PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
        return success(BeanUtils.toBean(pageResult, FileRespVO.class));
    }


    @PostMapping("/upload-file")
    @Operation(summary = "上传文件")
    public CommonResult<FileUploadResult> uploadAttach(FileUploadReqVO uploadReqVO) throws Exception {
        //检查文件限制
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(ATTACH_TYPE), null);
        return success(fileUploadResult);
    }

    @PostMapping("/ueditor-upload")
    @Operation(summary = "上传文件", description = "模式一：后端上传文件")
    public UeditorUploadResult uEditorUploadFile(FileUploadReqVO uploadReqVO) throws Exception {
        MultipartFile file = uploadReqVO.getFile();
        String path = uploadReqVO.getPath();
        String url = fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()));
        UeditorUploadResult uploadResult = new UeditorUploadResult();
        uploadResult.setUrl(url);
        uploadResult.setState("SUCCESS");
        uploadResult.setTitle(FileNameUtil.getName(url));
        uploadResult.setOriginal(file.getOriginalFilename());
        return uploadResult;
    }


    /**
     * 上传图片
     */
    @PostMapping("/upload-image")
    @Operation(summary = "上传图片文件")
    public CommonResult<FileUploadResult> imageUpload(FileUploadReqVO uploadReqVO) throws Exception {
        //图片水印
        CommonResult<SiteConfigDto> siteConfigResp = siteConfigApi.getSiteConfig(SiteUtil.getSiteId(ServletUtils.getRequest()));
        if (siteConfigResp.isError()) {
            throw exception(FILE_CONFIG_RPC_ERROR);
        }
        SiteConfigDto siteConfigDto = siteConfigResp.getData();
        if (siteConfigDto == null) {
            throw exception(SITE_CONFIG_NULL);
        }
        SiteConfigWatermarkDto watermarkSet = siteConfigDto.getWatermark();
        FileHandler imageHandler = (String fileExt, InputStream inputStream) -> {
            final String fileType = fileExt;
            FileHandlerUploadResult handlerUploadResult = new FileHandlerUploadResult();
            File tempFile = Files.createTempFile(null, "." + fileType).toFile();
            FileUtil.writeFromStream(inputStream, tempFile);
            File tempFileForScale = Files.createTempFile(null, "." + fileType).toFile();
            //上传到富文本并且站点配置了图片最大宽度则缩放图片
            final Integer editorPicMaxWidth = siteConfigDto.getBase().getEditorPicMaxWidth();
            if (uploadReqVO.getUploadEditor() != null && uploadReqVO.getUploadEditor() && editorPicMaxWidth != null) {
                final BufferedImage bufferedImage = ImgUtil.read(tempFile);
                final int originWidth = bufferedImage.getWidth();
                if (originWidth > editorPicMaxWidth) {
                    final float scale = editorPicMaxWidth / (originWidth * 1.0f);
                    ImgUtil.scale(tempFile, tempFileForScale, scale);
                    handlerUploadResult.setProcessInputStream(new FileInputStream(tempFileForScale));
                }
            }
            String tempTargetFile = FileUtils.getTempDirectoryPath() + File.separator + IdUtil.fastUUID();
            //处理图片水印
            if (!EnumWaterMarkType.NOTHING.getType().equals(watermarkSet.getWatermarkSet())) {
                try {
                    //图片水印
                    if (EnumWaterMarkType.PICTURE.getType().equals(watermarkSet.getWatermarkSet())) {
                        File waterTempFile = Files.createTempFile("water", ".png").toFile();
                        //不存在在下载
                        if (!waterTempFile.exists()) {
                            URL url = URLUtil.url(watermarkSet.getWatermarkPictureUrl());
                            InputStream in = null;
                            try {
                                in = url.openStream();
                                // 临时文件缓存下名称
                                FileUtil.writeFromStream(in, waterTempFile);
                            } catch (IOException e) {
                                throw new IORuntimeException(e);
                            } finally {
                                IoUtil.close(in);
                            }
                        }
                        //暂不透明
                        ImageUtil.watermark(tempFile.getAbsolutePath(), tempTargetFile, waterTempFile.getAbsolutePath(), watermarkSet.getWatermarkPosition(), 100);
                    } else {
                        //文字水印处理
                        ImageUtil.pressText(tempFile.getAbsolutePath(), tempTargetFile, watermarkSet.getWatermarkTxt(), watermarkSet.getWatermarkTxtColour(),
                                watermarkSet.getWatermarkTxtSize(), watermarkSet.getWatermarkPosition(), 100);
                    }
                    handlerUploadResult.setProcessInputStream(new FileInputStream(tempTargetFile));
                } finally {
                    FileUtils.deleteQuietly(tempFile);
                    FileUtils.deleteQuietly(new File(tempTargetFile));
                    FileUtils.deleteQuietly(tempFileForScale);
                }
            } else {
                try {
                    handlerUploadResult.setProcessInputStream(new FileInputStream(tempFile));
                } finally {
                    FileUtils.deleteQuietly(tempFile);
                    FileUtils.deleteQuietly(tempFileForScale);
                }
            }
            return handlerUploadResult;
        };
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(IMAGE_TYPE), imageHandler);
        return success(fileUploadResult);
    }

    /**
     * 上传图片
     */
    @PostMapping("/upload-image-simple")
    @Operation(summary = "上传图片文件")
    public FileUploadResult simpleImageUpload(FileUploadReqVO uploadReqVO) throws Exception {
        final CommonResult<FileUploadResult> fileUploadResult = imageUpload(uploadReqVO);
        if (fileUploadResult.isSuccess()) {
            return fileUploadResult.getData();
        }
        return null;
    }

    @PostMapping("/upload-video")
    public CommonResult<FileUploadResult> videoUpload(FileUploadReqVO uploadReqVO) throws Exception {
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(VIDEO_TYPE), null);
        VideoAttachVo videoDurationAndPoster = getVideoDurationAndPoster(uploadReqVO.getFile());
        fileUploadResult.setDuration(videoDurationAndPoster.getDuration());
        fileUploadResult.setPosterUrl(videoDurationAndPoster.getPosterUrl());
        return success(fileUploadResult);
    }

    @PostMapping("/upload-video-simple")
    public SimpleVideoUploadResultVO simpleVideoUpload(FileUploadReqVO uploadReqVO) throws Exception {
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(VIDEO_TYPE), null);
        SimpleVideoUploadResultVO vo = new SimpleVideoUploadResultVO();
        vo.setName(fileUploadResult.getName());
        vo.setUrl(fileUploadResult.getUrl());
        vo.setSize(fileUploadResult.getSize());
        return vo;
    }

    @PostMapping("/upload-audio")
    public CommonResult<FileUploadResult> audioUpload(FileUploadReqVO uploadReqVO) throws Exception {
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(AUDIO_TYPE), null);
        fileUploadResult.setDuration(getDuration(uploadReqVO.getFile()));
        return success(fileUploadResult);
    }

    @PostMapping("/upload-doc")
    public CommonResult<FileUploadResult> docUpload(FileUploadReqVO uploadReqVO) throws Exception {
        FileUploadResult fileUploadResult = upload(uploadReqVO, getFileValidVo(LIBRARY_TYPE), null);
        FileDO fileDO = fileService.getFileByUrl(fileUploadResult.getUrl());

        String extension = FilenameUtils.getExtension(fileDO.getPath());
        File tempSourceFile = Files.createTempFile(null, "." + extension).toFile();
        FileUtil.writeBytes(uploadReqVO.getFile().getBytes(), tempSourceFile);
        if (tempSourceFile == null) {
            throw exception(FILE_NOT_EXISTS);
        }
        if (docConvertService == null) {
            throw exception(OFFICE_TOOL_NOTSTART);
        }
        //生成临时文件用于转换
        File tempPdfFile = Files.createTempFile(null, ".pdf").toFile();

        //pdf 封面图
        File tempPdfCoverFile = Files.createTempFile(null, ".jpg").toFile();
        try {
            //转换pdf
            docConvertService.convertToPdf(tempSourceFile, tempPdfFile);
            String pdfUrl = fileService.createFile(tempPdfFile.getName(), uploadReqVO.getPath(), IoUtil.readBytes(new FileInputStream(tempPdfFile)));
            fileUploadResult.setDocPdf(pdfUrl);
            //提取pdf封面
            PdfUtil.pickupCover(tempPdfFile, tempPdfCoverFile);
            String pdfConverUrl = fileService.createFile(tempPdfCoverFile.getName(), uploadReqVO.getPath(), IoUtil.readBytes(new FileInputStream(tempPdfCoverFile)));
            fileUploadResult.setDocName(fileUploadResult.getName());
            fileUploadResult.setOriginDoc(fileUploadResult.getUrl());
            fileUploadResult.setDocImage(pdfConverUrl);
        } finally {
            if (tempSourceFile.exists()) {
                FileUtils.deleteQuietly(tempSourceFile);
            }
            if (tempPdfFile.exists()) {
                FileUtils.deleteQuietly(tempPdfFile);
            }
            if (tempPdfCoverFile.exists()) {
                FileUtils.deleteQuietly(tempPdfCoverFile);
            }
        }

        return success(fileUploadResult);
    }

    @PostMapping("/import-doc")
    public CommonResult<String> importDoc(FileUploadReqVO uploadReqVO) throws IOException {
        Assert.notNull(uploadReqVO.getFile(), () -> {
            throw exception(FILE_NOT_EXISTS);
        });
        if (docConvertService == null) {
            throw exception(OFFICE_TOOL_NOTSTART);
        }
        InputStream inputStream = uploadReqVO.getFile().getInputStream();
        File tempDocFile = FileUtil.createTempFile();
        File tempHtmlFile = FileUtil.createTempFile();
        String docContent = EMPTY;
        try {
            FileUtil.writeFromStream(inputStream, tempDocFile);
            docConvertService.convertToHtml(tempDocFile, tempHtmlFile);
            //加载html文件 body的内容 因为转换的内容在body里面
            Document document = Jsoup.parse(tempHtmlFile, Charset.defaultCharset().name());
            Elements body = document.body().getElementsByTag("body");
            docContent = body.html();
            Document parse = Jsoup.parseBodyFragment(docContent);
            //解析传递字符串  parse 包含
            Elements imgs = parse.getElementsByTag("img");
            String parentPath = tempHtmlFile.getParentFile().getAbsolutePath();
            for (Element img : imgs) {
                //上传临时文件并替换
                String linkSrc = img.attr("src");
                File tmpImgFile = null;
                try {
                    tmpImgFile = new File(parentPath + File.separator + linkSrc);
                    String url = fileService.createFile(linkSrc, null, IoUtil.readBytes(new FileInputStream(tmpImgFile)));
                    img.attr("src", url);
                } finally {
                    if (tmpImgFile != null && tmpImgFile.exists()) {
                        FileUtils.deleteQuietly(tmpImgFile);
                    }
                }
            }
            docContent = parse.html();
        } finally {
            if (tempDocFile.exists()) {
                FileUtils.deleteQuietly(tempDocFile);
            }
            if (tempHtmlFile.exists()) {
                FileUtils.deleteQuietly(tempHtmlFile);
            }
        }

        return success(docContent);
    }

    @PostMapping("image-crop")
    public CommonResult<ImgCutVo> imageCrop(@RequestBody ImgCutDto params) throws Exception {
        FileDO fileDO = fileService.getFileByUrl(params.url);
        if (fileDO == null) {
            throw exception(FILE_NOT_EXISTS);
        }
        // 图片裁剪。图片任意裁剪，生成新图片。
        String extension = FilenameUtils.getExtension(fileDO.getPath());
        // 读取内容
        byte[] content;
        try {
            content = fileService.getFileContent(fileDO.getConfigId(), fileDO.getPath());
        } catch (Exception e) {
            throw exception(FILE_NOT_EXISTS);
        }
        if (content == null) {
            log.warn("[getFileContent][configId({}) path({}) 文件不存在]", fileDO.getConfigId(), fileDO.getPath());
            throw exception(FILE_NOT_EXISTS);
        }
        //从数据库记录拿到文件
        File file = Files.createTempFile(null, "." + extension).toFile();
        FileUtil.writeBytes(content, file);
        if (file == null) {
            throw exception(FILE_NOT_EXISTS);
        }
        //生成临时文件用于裁剪
        File tempFile = Files.createTempFile(null, "." + extension).toFile();
        try {
            // 裁剪
            if (ResizeMode.scale.equals(params.getMode())) {
                //暂时0.5压缩
                ImageUtil.scale(file.getAbsolutePath(), tempFile.getAbsolutePath(), 0.5f);
            } else if (ResizeMode.resize.equals(params.getMode())) {
                ImageUtil.resize(file.getAbsolutePath(), tempFile.getAbsolutePath(), params.getWidth(), params.getHeight());
            } else {
                ImageUtil.cut(file.getAbsolutePath(), tempFile.getAbsolutePath(), params.getX(), params.getY(), params.getWidth(), params.getHeight());
            }
            //上传裁剪后的临时文件
            final CommonResult<GlobalConfigRespVO> globalConfigRespVOCommonResult = globalConfigApi.getGlobalConfig();
            if(globalConfigRespVOCommonResult.isError()){
                throw exception(FILE_CONFIG_NULL);
            }
            String fileUrl = fileService.createFile(globalConfigRespVOCommonResult.getData().getUploadStoreConfigId().longValue()
                    ,fileDO.getName(), fileDO.getPath(), FileUtil.readBytes(tempFile));
            return success(new ImgCutVo(fileUrl));
        } finally {
            if (file.exists()) {
                FileUtils.deleteQuietly(file);
            }
            if (tempFile.exists()) {
                FileUtils.deleteQuietly(tempFile);
            }
        }
    }

    /**
     * 抓取远程图片
     */
    @PostMapping(value = "image-remote-down", consumes = "text/plain")
    public CommonResult<ImgRemoteDownVo> imageFetch(@RequestBody String source) {
        try {
            URL src = new URL(source);
            // 只支持 http 和 https 协议
            String protocol = src.getProtocol();
            String httpProtocol = "http";
            String httpsProtocol = "https";
            if (!httpProtocol.equals(protocol) && !httpsProtocol.equals(protocol)) {
                log.error("下载图片失败，url必须是http或https");
                throw exception(FILE_DOWN_IMG_ERROR);
            }
            String host = src.getHost();
            // 禁止访问本地网络
            InetAddress inetAddress = InetAddress.getByName(host);
            if (inetAddress.isSiteLocalAddress() || inetAddress.isAnyLocalAddress() ||
                    inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
                log.error("下载图片失败，禁止访问本地网络");
                throw exception(FILE_DOWN_IMG_ERROR);
            }
            // 使用UriComponentsBuilder重新构建url，防止攻击
            src = UriComponentsBuilder.newInstance().scheme(protocol).host(host)
                    .path(src.getPath()).query(src.getQuery()).build().toUri().toURL();
            HttpURLConnection conn = (HttpURLConnection) src.openConnection();
            conn.setInstanceFollowRedirects(false);
            // responseCode 必须为 200
            int responseCode = conn.getResponseCode();
            if (responseCode != HttpServletResponse.SC_OK) {
                log.error("下载图片失败，responseCode必须是200");
                throw exception(FILE_DOWN_IMG_ERROR);
            }
            // contentType 只支持 image/jpeg image/gif image/png image/bmp image/webp
            String contentType = conn.getContentType();
            String extension = ImageUtil.getImageExtension(contentType);
            if (extension == null) {
                log.error("下载图片失败，Only supports image contentType: " + contentType);
                throw exception(FILE_DOWN_IMG_ERROR);
            }
            //图片水印
            CommonResult<SiteConfigDto> siteConfigResp = siteConfigApi.getSiteConfig(SiteUtil.getSiteId(ServletUtils.getRequest()));
            if (siteConfigResp.isError()) {
                throw exception(FILE_CONFIG_RPC_ERROR);
            }
            SiteConfigDto siteConfigDto = siteConfigResp.getData();
            if (siteConfigDto == null) {
                throw exception(SITE_CONFIG_NULL);
            }
            SiteConfigWatermarkDto watermarkSet = siteConfigDto.getWatermark();
            FileHandler imageHandler = (String fileExt, InputStream inputStream) -> {
                final String fileType = fileExt;
                FileHandlerUploadResult handlerUploadResult = new FileHandlerUploadResult();
                File tempFile = Files.createTempFile(null, "." + fileType).toFile();
                FileUtil.writeFromStream(inputStream, tempFile);
                File tempFileForScale = Files.createTempFile(null, "." + fileType).toFile();
                String tempTargetFile = FileUtils.getTempDirectoryPath() + File.separator + IdUtil.fastUUID();
                //处理图片水印
                if (!EnumWaterMarkType.NOTHING.getType().equals(watermarkSet.getWatermarkSet())) {
                    try {
                        //图片水印
                        if (EnumWaterMarkType.PICTURE.getType().equals(watermarkSet.getWatermarkSet())) {
                            File waterTempFile = Files.createTempFile("water", ".png").toFile();
                            //不存在在下载
                            if (!waterTempFile.exists()) {
                                URL url = URLUtil.url(watermarkSet.getWatermarkPictureUrl());
                                InputStream in = null;
                                try {
                                    in = url.openStream();
                                    // 临时文件缓存下名称
                                    FileUtil.writeFromStream(in, waterTempFile);
                                } catch (IOException e) {
                                    throw new IORuntimeException(e);
                                } finally {
                                    IoUtil.close(in);
                                }
                            }
                            //暂不透明
                            ImageUtil.watermark(tempFile.getAbsolutePath(), tempTargetFile, waterTempFile.getAbsolutePath(), watermarkSet.getWatermarkPosition(), 100);
                        } else {
                            //文字水印处理
                            ImageUtil.pressText(tempFile.getAbsolutePath(), tempTargetFile, watermarkSet.getWatermarkTxt(), watermarkSet.getWatermarkTxtColour(),
                                    watermarkSet.getWatermarkTxtSize(), watermarkSet.getWatermarkPosition(), 100);
                        }
                        handlerUploadResult.setProcessInputStream(new FileInputStream(tempTargetFile));
                    } finally {
                        FileUtils.deleteQuietly(tempFile);
                        FileUtils.deleteQuietly(new File(tempTargetFile));
                        FileUtils.deleteQuietly(tempFileForScale);
                    }
                } else {
                    try {
                        handlerUploadResult.setProcessInputStream(new FileInputStream(tempFile));
                    } finally {
                        FileUtils.deleteQuietly(tempFile);
                        FileUtils.deleteQuietly(tempFileForScale);
                    }
                }
                return handlerUploadResult;
            };
            final FileValidVo fileValidVo = getFileValidVo(IMAGE_TYPE);
            String fileName = IdUtil.getSnowflake().nextIdStr() + "." + extension;
            try (InputStream is = conn.getInputStream()) {
                FileUploadResult fileUploadResult = upload(fileValidVo.getUploadStoreConfigId().longValue(), fileName, null, is, imageHandler);
                ImgRemoteDownVo remoteDownVo = new ImgRemoteDownVo(source, fileUploadResult.getUrl());
                return success(remoteDownVo);
            }
        } catch (Exception e) {
            log.error("Image upload error!", e);
            throw exception(FILE_DOWN_IMG_ERROR);
        }
    }

    private FileUploadResult upload(FileUploadReqVO uploadReqVO, FileValidVo validVo, @Nullable FileHandler fileHandler) throws Exception {
        MultipartFile multipart = uploadReqVO.getFile();
        // 检查文件大小
        long length = multipart.getSize();
        validateLimit(validVo.getLimitByte(), length);
        // 检查黑名单文件后缀
        String extension = FilenameUtils.getExtension(multipart.getOriginalFilename());
        if (jproProperties.getUploadsExtensionExcludes().contains(StringUtils.lowerCase(extension))) {
            throw exception(FILE_LIMIT_TYPE);
        }
        // 检查文件后缀
        validateType(validVo.getLimitTypes(), extension);

        return upload(validVo.getUploadStoreConfigId().longValue(),uploadReqVO.getFile().getOriginalFilename(), uploadReqVO.getPath(), uploadReqVO.getFile().getInputStream(), fileHandler);
    }

    private FileUploadResult upload(Long clientId,String originFileName, String path, InputStream inputStream, @Nullable FileHandler fileHandler) throws Exception {
        final String suffix = FileNameUtil.getSuffix(originFileName);
        File tempFile = FileUtil.createTempFile("." + suffix, true);
        FileCopyUtils.copy(IoUtil.readBytes(inputStream), tempFile);
        String url;
        FileHandlerUploadResult handleResult = null;
        if (fileHandler != null) {
            /**
             * 文件额外处理处理，比如图片水印
             */
            handleResult = fileHandler.handle(suffix, new FileInputStream(tempFile));
            url = fileService.createFile(clientId, originFileName, path, IoUtil.readBytes(handleResult.getProcessInputStream()));
        } else {
            url = fileService.createFile(clientId,originFileName, path, FileUtil.readBytes(tempFile));
        }
        FileDO fileDO = fileService.getFileByUrl(url);
        FileUploadResult fileUploadResult = FileDOConvert.INSTANCE.convert(fileDO);
        if (tempFile.exists()) {
            FileUtils.deleteQuietly(tempFile);
        }
        if (handleResult != null) {
            //null,表示无限制，true表示若父类中属性值为空则忽略，不传给子类
            final CopyOptions copyOption = CopyOptions.create(null, true);
            BeanUtil.copyProperties(handleResult, fileUploadResult, copyOption);
        }
        return fileUploadResult;
    }

    private FileValidVo getFileValidVo(String type) {
        CommonResult<GlobalConfigRespVO> globalConfigResp = globalConfigApi.getGlobalConfig();
        if (globalConfigResp.isError()) {
            throw exception(FILE_CONFIG_RPC_ERROR);
        }
        GlobalConfigRespVO globalConfigRespVO = globalConfigResp.getData();
        if (globalConfigRespVO == null) {
            throw exception(FILE_CONFIG_NULL);
        }
        UploadConfig uploadConfig = globalConfigRespVO.getUploadConfig();
        switch (type) {
            case IMAGE_TYPE:
                return new FileValidVo(uploadConfig.getImageLimitByte(), uploadConfig.getImageLimits(), IMAGE_TYPE, globalConfigRespVO.getUploadStoreConfigId());
            case VIDEO_TYPE:
                return new FileValidVo(uploadConfig.getVideoLimitByte(), uploadConfig.getVideoLimits(), VIDEO_TYPE,globalConfigRespVO.getUploadStoreConfigId());
            case AUDIO_TYPE:
                return new FileValidVo(uploadConfig.getAudioLimitByte(), uploadConfig.getAudioLimits(), AUDIO_TYPE,globalConfigRespVO.getUploadStoreConfigId());
            case LIBRARY_TYPE:
                return new FileValidVo(uploadConfig.getLibraryLimitByte(), uploadConfig.getLibraryLimits(), LIBRARY_TYPE,globalConfigRespVO.getUploadStoreConfigId());
            default:
                return new FileValidVo(uploadConfig.getAttachLimitByte(), uploadConfig.getAttachLimits(), ATTACH_TYPE,globalConfigRespVO.getUploadStoreConfigId());
        }
    }

    private Long getDuration(MultipartFile file) throws IOException {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        File tempFile = Files.createTempFile(null, "." + extension).toFile();
        FileUtil.writeBytes(file.getBytes(), tempFile);
        MultimediaObject multimediaObject = new MultimediaObject(tempFile);
        Long duration;
        try {
            duration = MediaUtil.getDuration(multimediaObject);
            return duration / 1000;
        } catch (EncoderException e) {
            throw new RuntimeException(e);
        } finally {
            FileUtils.deleteQuietly(tempFile);
        }
    }

    /**
     * 获取视频时长并截取图片
     *
     * @param file
     * @return
     * @throws IOException
     */
    private VideoAttachVo getVideoDurationAndPoster(MultipartFile file) throws IOException {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        File tempFile = Files.createTempFile(null, "." + extension).toFile();
        FileUtil.writeBytes(file.getBytes(), tempFile);
        MultimediaObject multimediaObject = new MultimediaObject(tempFile);
        Long duration = 0L;
        try {
            duration = MediaUtil.getDuration(multimediaObject);
            VideoAttachVo videoAttachVo = new VideoAttachVo();
            videoAttachVo.setDuration(duration / 1000);
            File tempPosterFile = Files.createTempFile(null, ".jpeg").toFile();
            MediaUtil.captureImage(multimediaObject, 3000, tempPosterFile);
            if (tempPosterFile.exists()) {
                String posterFileUrl = fileService.createFile(null, null, IoUtil.readBytes(new FileInputStream(tempPosterFile)));
                videoAttachVo.setPosterUrl(posterFileUrl);
            }
            return videoAttachVo;
        } catch (EncoderException e) {
            throw new RuntimeException(e);
        } finally {
            FileUtils.deleteQuietly(tempFile);
        }
    }

    public static void validateLimit(long limit, long fileSize) {
        if (limit != 0 && fileSize > limit) {
            throw exception(FILE_LIMIT_SIZE);
        }
    }

    public static void validateType(String types, @Nullable String extension) {
        if (!isValidType(types, extension)) {
            throw exception(FILE_LIMIT_TYPE);
        }
    }

    public static boolean isValidType(String types, @Nullable String type) {
        return Arrays.asList(types.toLowerCase().split(",")).contains(StringUtils.lowerCase(type));
    }


}
